<#
.Author
	cw50406
.Version
	1.3 -rewrite code to be faster for bulk vdi info collect (200s for 1482 vdis)
.ROLE
   ICG Support Team
.DESCRIPTION
	This is used to collect remote vdi config infomation with below types, the csv file will be saved in the same directory as well as send to the team:
		1. software version
		2. service running status
		3. ram/c driver size
	Meanings for short valus:
		R-1.7.0.101<-->N-java.exe means: found registry version but not found software exe
		R-1.7.0.101<-->N- means: the 'InstallLocation' value in its registry is empty
		R-1.7.0.91<-->P-1.7.0.101 means: registry version 1.7.0.91 not matches software exe version 1.7.0.101
		R-1.7.0.91<-->N-PV means: found registry version 1.7.0.91 but not found software exe version
		P-1.7.0.101<-->N-RV means: found software exe version 1.7.0.101 but not found registry version
.EXAMPLE 
	.\get_bulk_vdis_info.ps1
		this will pop-up text to let you enter vdi name
	.\get_bulk_vdis_info.ps1 -inputFileSaved
		this will run directly with vdi in inputlist.txt if you have saved them
#>
param(
	[Parameter(Mandatory=$false)]
	[string]$remote_script="get_single_vdi_info.ps1",         # the script to be copied to end VDI to be ran
	[Parameter(Mandatory=$false)]                             
	[string]$inputFile="inputlist.txt",                       # the input file to store vdi and fid list
	[switch]$inputFileSaved                                   # if inputlist.txt has been saved, if yes, it will run directly
)
Function Check-AdminRight(){ # this function is used for checking if the current user has admin access
	$cwi=[Security.Principal.WindowsIdentity]::GetCurrent()
	$cwp=[Security.Principal.WindowsPrincipal]$cwi
	if(!$cwp.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)){
		return $false
	}else{return $true}
}
Function Start-BulkThreads(){	# -Action { Stop-Process -Id $PID }
	# useing parallel threads to run scripts in the same time so that it will speedup the running duration
	#reference https://www.codeproject.com/tips/895840/multi-threaded-powershell-cookbook
    param(
    	[Parameter(Mandatory=$True)]
    	[Object[]]$vdilist,             				 # the vdi list that scripts to be ran
    	[Parameter(Mandatory=$True)]    
    	[ScriptBlock]$vdi_info_collect_action,           # the actual action that each thread will run
    	[Parameter(Mandatory=$True)]    
    	[AllowNull()]
    	[Object[]]$vdi_info_collect_params,      		 # the parameters that each thread needs, this can be a single or array of parameter
    	[Parameter(Mandatory=$false)]   
    	[int]$maxCnt=100                   				 # the default amount of powershell threads
    )
	$results=@() # this is used to collect the result from each thread
	$vdiCounts = $vdilist.Count
	If( $vdiCounts -eq 1 ){ # no need to create thread pool to run it
		If( [string]::IsNullOrWhiteSpace($vdilist[0]) ){
		    write-error "vdilist only contains 1 line but it's null"
		}else{& $vdi_info_collect_action $vdilist[0] 1 $vdi_info_collect_params}
	}else{
		$vdiCounts = $vdilist.Count ; $throttleLimit=$maxCnt ; If($vdiCounts -le $maxCnt ){$throttleLimit=$vdiCounts}
		# creaate the thread pool and run each thread based the vdi list amount
		$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
		$Pool = [runspacefactory]::CreateRunspacePool(1, $throttleLimit, $SessionState, $Host)
		$Pool.Open() ; $threads = @() ; $cnt=1
		$handles = foreach($line in $vdilist){
			if( [string]::IsNullOrWhiteSpace($line ) ){continue}
			$powershell = [powershell]::Create().AddScript($vdi_info_collect_action).AddArgument($line).AddArgument($cnt).AddArgument($vdi_info_collect_params)
			$powershell.RunspacePool = $Pool ;	$powershell.BeginInvoke() ; $threads += $powershell ; $cnt++
		}
		# wait each thread to be done and then collecte its return value
		do{
		  $i = 0 ;  $done = $true
		  foreach ($handle in $handles) {
			If($handle -ne $null){if($handle.IsCompleted){$results+=$threads[$i].EndInvoke($handle);$threads[$i].Dispose();$handles[$i]=$null}else{$done = $false}}
			$i++
		  }
		  if (-not $done) { Start-Sleep -Milliseconds 200 }
		} until ($done)
	}
	return $results # return the value collection from each thread and then export to csv or take other further actions
}
$st=get-date
If(!(Check-AdminRight)){write-error "Admin right is required to run this script!";return}
$this_path = Split-Path -Parent ($MyInvocation.MyCommand.Definition) # get the directory of this running script
$this_user=$env:username
$this_machine=$env:computername
$temp_path="$this_path\temp" # used to store each result csv file from end vdi incase the script breaks in the middle of running unexpectedly so that we can check the file and know which ones we have already checked
del "$temp_path\*.csv" -Force -ErrorAction Ignore
If(!(test-path $temp_path -ErrorAction Ignore )){new-item -ItemType Directory -path $temp_path -Force 1>$null}
$remote_script="$this_path\$remote_script" # set the remote script full path
If(!(test-path $remote_script) ){ #exit if remote script not exists in this path
	write-error "$remote_script was not found";return
}
# open inputlist.txt(create it if it not exists) and let user to enter the vdi list.
$inputFile="$this_path\$inputFile" ; If(!(test-path $inputFile )){new-item -ItemType File -Path $inputFile -force 1>$null}
$inputlist = get-content $inputFile |select -Unique|?{$_.trim() -ne ""}|sort
If(($PSBoundParameters.ContainsKey('inputFileSaved') -and $inputlist.count -eq 0) -or !$inputFileSaved ){
	clear-content $inputFile -force -ErrorAction Ignore
	Do{ # loop above action if the vdi list is empty 
		write-host "Please save vdi(fid) list as below format in opened txt file each line as it's empty, please press 'Ctrl+C' if you want to quit:
		MWDINTVM30544
		MWDINTVM30532"; notepad "$this_path\inputlist.txt"; read-host " "
		# get unique vdi name (exclude empty/blank spaces line)
		$inputlist = (get-content $inputFile |select -Unique|?{$_.trim() -ne ""}|sort)
	}While( $inputlist -eq $null  )
}
# initialize and clear error log(create it if it not exists) to store error info for further actions
$err_log = "$this_path\err.log"
If(!(test-path $err_log -ErrorAction Ignore)){
	new-item -ItemType File -path $err_log -Force 1>$null
}else{Clear-Content $err_log -Force -ErrorAction Ignore}
$vdi_info_task_action={ # the actual actions that each powershell thread will run in end VDI
	param($vdi,$cnt,$vdi_info_collect_task_params)
	#<------------------------------ initialize parameters
	write-host "Starting to copy script and create task in $cnt-$vdi ...`n"
	$remote_vdi_temp_path = "\\$vdi\c$\temp"
	$this_path,$remote_script=$vdi_info_collect_task_params
	$err_log="$this_path\err.log"
	$remote_script_name=(ls $remote_script).name
	# remote csv file that will store the result of this action value
	$remote_csv_file="{0:yyyyMMddHHmmss}-{1}.csv" -f (Get-Date),(Get-Random -Maximum 10000)
	If(Test-Connection $vdi -quiet -count 1 -ErrorAction Ignore){ # Checking if vdi is online"
		Try{ # check if remote VDI Temp Path exists, this will aslo check if user has access to this VDI
		    If(!(test-path $remote_vdi_temp_path -ea 1)){new-item -ItemType Directory -path $remote_vdi_temp_path -Force 1> $null}
		}Catch{
		    If($_.Exception -like "*Access is denied*"){
				Write-Host -ForegroundColor Red "$vdi-$cnt Access Denied."
				"$vdi-$cnt Access Denied" >> $err_log ; return $null
			}
		}Finally{}
		$runScript="'C:\temp\$remote_script_name $remote_csv_file'"
		copy $remote_script "$remote_vdi_temp_path\$remote_script_name" -Force -ErrorAction Ignore # copy script to remote vdi
		Try{ # create the task to run the script in end VDI
			SCHTASKS /create /s $vdi /RU System /tn GetVDIConfigInfo /sc ONCE  /st (get-date).tostring("HH:mm") /F `
			/tr "powershell.exe -Executionpolicy unrestricted $runScript" 1>$null 2>&1
		}Catch{
		    If($_.Exception -like "*Cannot create a file when that file already exists*"){ 
				"$vdi-$cnt Cannot create a file when that file already exists" >> $err_log ; return
			}
		}Finally{}
		SCHTASKS /Run /s $vdi /TN GetVDIConfigInfo 1>$null 2>&1 ; write-host -ForegroundColor Yellow "----Done running task in $cnt-$vdi----`n"
		return "$vdi,$remote_csv_file,$remote_script_name"
    }else{ # VDI is offline
		"$vdi-$cnt is either offline or not valid" >> $err_log ; return $null
	}
}
[string[]]$run_vdi_info_list=Start-BulkThreads $inputlist $vdi_info_task_action $this_path,$remote_script
$vdi_info_collect_action={
	param($line,$cnt,$vdi_info_collect_action_params)
	$vdi,$remote_csv_file,$remote_script_name=$line.split(',')
	$this_path,$err_log=$vdi_info_collect_action_params
	$remote_vdi_temp_path="\\$vdi\c$\temp"
	#<------------------------------ initialize parameters
	write-host "Starting to collect $cnt-$vdi info...`n"
	Do{ # retrive csv file from end VDI
		Try{
			# get the vdi configure value from the csv file and copy this csv file to temp folder in this running directory
			$vdi_info=import-csv "$remote_vdi_temp_path\$remote_csv_file" -ErrorAction Stop
			copy "$remote_vdi_temp_path\$remote_csv_file" "$this_path\temp\" -force -ea 0
			$remote_script_name,$remote_csv_file|%{del "$remote_vdi_temp_path\$_" -force -ea 0}
			SCHTASKS /delete /s $vdi /TN GetVDIConfigInfo /F 1>$null 2>&1 
			Write-Host -ForegroundColor Yellow "----Done collecting $cnt-$vdi info----`n"
			$rst=$true
		}Catch{
			Sleep 5; $rst=$false
			If($loopcnt -gt 11){ # unable to find the result csv file after 10s
				"$vdi-$cnt Waiting too long(1 min) to import csv file." >> $err_log ; return}
		}Finally{$loopcnt++}
	}While(!$rst)
	return $vdi_info
}
$results=Start-BulkThreads $run_vdi_info_list $vdi_info_collect_action $this_path,$err_log
Write-Host "Try to export total result to csv file"
$all_vdi_info_csv="$this_path\all_vdi_info.csv"
Try{
	$results|Export-Csv -Path $all_vdi_info_csv -UseCulture -NoTypeInformation -Encoding UTF8 -Force -ErrorAction Stop
	# cw50406@imcnam.ssmb.com  dl.gt.cn.cstc.rpa.aa.ste@imcnam.ssmb.com
	Send-MailMessage `
		-From ($this_user+"."+$this_machine+"@imcnam.ssmb.com") `
		-To dl.gt.cn.cstc.rpa.aa.ste@imcnam.ssmb.com `
		-Subject "RPA AA STE VDI Info Collect - Success" `
		-Body "Hi RPA AA STE Support, `n`nPlease check attahed file for the VDI Config Details.`n`nThank You! `n`n$this_user" `
		-Attachments $all_vdi_info_csv,$err_log `
		-SmtpServer "imta.citicorp.com" -Port 25 `
		-Encoding([System.Text.Encoding]::UTF8)
	$run_time=([math]::Round(((get-date)-$st).totalseconds))
	Write-Host -ForegroundColor Yellow "The script finished in $run_time s."
}Catch{
	$_
}Finally{}
